/**
 *	@file	sslutil.c
 *
 *	@brief	SSL utilities function, like get certificate information.
 *	
 *	@author	Forrest.zhang	
 *
 *	@date
 */

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

#include <openssl/ssl.h>
#include <openssl/x509.h>

#include "ssl_util.h"

#define	_SU_ERR(f, a...)	printf("<%s-%d>: "f, __FILE__, __LINE__, ##a)
#define	_SU_ERR_RET(r, f, a...)					\
	({							\
		printf("<%s-%d>: "f, __FILE__, __LINE__, ##a);	\
		return r;					\
	})
#define	_SU_ERR_STR		strerror(errno)


/**
 *	The SSL private data stored in SSL
 */
typedef struct ssl_info {
	ssl_ctx_t	*ctx;		/* ssl context object */
	ssl_sd_e	side;		/* client/server side */
	int		num_handshake;	/* number of handshake */
	int		in_handshake;	/* in handshake process */
	int		in_renegotiate;	/* in renegotiate process */
} ssl_info_t;


/**
 *	Get a temp DH, it's copy from openssl/s_server.c
 */
static unsigned char _su_dh512_p[] = {
	0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
	0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
	0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
	0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
	0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
	0x47,0x74,0xE8,0x33,
};
static unsigned char _su_dh512_g[] = {
	0x02,
};

/* the lock for openssl thread-safe */
static pthread_mutex_t	*_su_locks;

/**
 *	Get DH 512 bit data
 */
DH *
_su_get_dh512(void)
{
	DH *dh=NULL;

	if ((dh=DH_new()) == NULL)
		return(NULL);

	dh->p=BN_bin2bn(_su_dh512_p, sizeof(_su_dh512_p), NULL);
	dh->g=BN_bin2bn(_su_dh512_g, sizeof(_su_dh512_g), NULL);

	if ((dh->p == NULL) || (dh->g == NULL))
		return(NULL);

	return(dh);
}

/**
 *	Thread ID for Openssl thread safe
 */
unsigned long
_su_thread_id(void)
{
	pthread_t id;

	id = pthread_self();

	return (unsigned long)id;
}

/**
 *	Lock for Openssl thread safe
 */
void
_su_lock_callback(int mode, int n, const char *file, int line)
{
	if (mode & CRYPTO_LOCK) 
		pthread_mutex_lock(&(_su_locks[n]));
	else 
		pthread_mutex_unlock(&(_su_locks[n]));
}

/**
 *	PEM format cert/privatekey file password callback function
 *
 */
static int 
_su_pem_password_cb(char *buf, int size, int rwflag, void *u)
{
	char *passwd;
	size_t plen;

	passwd = (char *)u;
	if (!passwd)
		return 0;

	plen = strlen(passwd);
	if (plen < 1 || plen > size)
                return 0;

        memcpy(buf, passwd, plen);

        return plen;
}

static int 
_su_verify_cb(int result, X509_STORE_CTX *xs)
{
	printf("result is %d\n", result);

	switch (xs->error) {

	/* no CRL file found */
	case X509_V_ERR_UNABLE_TO_GET_CRL:
		return 1;

	default:
		break;
	}

	return result;
}

static int 
_su_noverify_cb(int result, X509_STORE_CTX *xs)
{
	return 1;
}

static void 
_su_info_cb_print(const SSL *ssl, int type, int val)
{
	ssl_info_t *si;
	const char *str;
	int side = type & ~SSL_ST_MASK;

	if (!ssl) {
		_SU_ERR("no SSL object\n");
		return;
	}
	
	si = SSL_get_app_data(ssl);
	if (!si) {
		_SU_ERR("no ssl_info object\n");
		return;
	}

	if (side & SSL_ST_CONNECT) {
		str = "SSL_connect: ";
	}
	else if (side & SSL_ST_ACCEPT) {
		str = "SSL_accept: ";
	}
	else {
		str = "unknowed side: ";
	}
	
	if (type & SSL_CB_LOOP) {
		printf("LOOP:%s:%s\n", str, SSL_state_string_long(ssl));
	}
	else if (type & SSL_CB_ALERT) {
		str = (type & SSL_CB_READ) ? "read" : "write";
		printf("ALERT:%s:%s:%s\n", str, 
		       SSL_alert_type_string_long(val),
		       SSL_alert_desc_string_long(val));
	}
	else if (type & SSL_CB_EXIT) {
		if (val == 0) {
			printf("EXIT(%d):%s:failed in %s\n", val, str, 
			       SSL_state_string_long(ssl));
		}
		else if (val < 0) {
			printf("EXIT(%d):%s:error in %s\n", val, str, 
			       SSL_state_string_long(ssl));
		}
		else {
			printf("EXIT(%d):%s:state in %s\n", val, str, 
			       SSL_state_string_long(ssl));
		}
	}
	else if (type & SSL_CB_HANDSHAKE_START) {
		si->num_handshake++;
		si->in_handshake = 1;
		printf("HANDSHAKE_START\n");
	}
	else if (type & SSL_CB_HANDSHAKE_DONE) {
		si->in_handshake = 0;
		printf("HANDSHAKE_DONE\n");
	}
	else if (type & SSL_CB_READ) {
		printf("READ(%d):%s:%s\n", val, str, 
		       SSL_state_string_long(ssl));
	}
	else if (type & SSL_CB_WRITE) {
		printf("WRITE(%d):%s:%s\n", val, str, 
		       SSL_state_string_long(ssl));
	}
	else {
		return;
	}
}

static void 
_su_info_cb(const SSL *ssl, int type, int val)
{
	ssl_info_t *si;

	/* check ssl */
	if (!ssl) {
		_SU_ERR("no SSL object\n");
		return;
	}
	
	/* check ssl_info */
	si = SSL_get_app_data(ssl);
	if (!si) {
		_SU_ERR("no ssl_info object\n");
		return;
	}

	if (type & SSL_CB_HANDSHAKE_START) {
		si->num_handshake++;
		si->in_handshake = 1;
	}
	else if (type & SSL_CB_HANDSHAKE_DONE) {
		si->in_handshake = 0;
	}
}

static int 
_su_sni_cb(SSL *ssl, int *ad, void *arg)
{
	ssl_ctx_t *sc;
	ssl_info_t *si;
	const char *sname;

	if (!ssl) 
		_SU_ERR_RET(SSL_TLSEXT_ERR_NOACK, "not found ssl\n");
	
	si = SSL_get_app_data(ssl);
	if (!si) 
		_SU_ERR_RET(SSL_TLSEXT_ERR_NOACK, "not found ssl_info\n");
	
	sc = si->ctx;
	if (!sc) 
		_SU_ERR_RET(SSL_TLSEXT_ERR_NOACK, "not found ssl_ctx\n");

	sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
	if (!sname) {
		return SSL_TLSEXT_ERR_OK;
	}

	while (sc) {
		if (sc->domain[0] && strcmp(sc->domain, sname) == 0) {
			SSL_set_SSL_CTX(ssl, sc->ctx);
			break;
		}
		sc = sc->next;
	}

	return SSL_TLSEXT_ERR_OK;
}

/**
 *	
 *
 *	Return X509 object if success, NULL on error.
 */
static X509 * 
_su_load_x509(const char *certfile)
{
	X509 *x509 = NULL;
	FILE *fp = NULL;
	
	if (!certfile)
		_SU_ERR_RET(NULL, "invalid argument\n");

	fp = fopen(certfile, "r");
	if (!fp)
		_SU_ERR_RET(NULL, "open certfile %s failed: %s\n", 
			    certfile, _SU_ERR_STR);

	/* try to decode PEM format certificate */
	x509 = PEM_read_X509(fp, NULL, NULL, NULL);
	if (x509) {
		fclose(fp);
		return x509;
	}
	fseek(fp, 0L, SEEK_SET);

	/* the DER format X509 certificate file */
	x509 = d2i_X509_fp(fp, NULL);

	fclose(fp);
	return x509;
}

/**
 *	Load a EVP_PKEY object from private key file @keyfile, if key file 
 *	is PEM format and encrypted, the password is stored in @password
 *
 *	Return EVP_PKEY object if success, NULL on error.
 */
static EVP_PKEY * 
_su_load_pkey(const char *keyfile, const char *password)
{
	EVP_PKEY *pkey;
	FILE *fp;
	pem_password_cb *cb = NULL;
	
	if (!keyfile)
		_SU_ERR_RET(NULL, "invalid argument @keyfile\n");

	if (password)
		cb = _su_pem_password_cb;

	fp = fopen(keyfile, "r");
	if (!fp)
		_SU_ERR_RET(NULL, "open keyfile %s failed: %s\n", 
			    keyfile, _SU_ERR_STR);

	/* try to read PEM format private key file */
	pkey = PEM_read_PrivateKey(fp, NULL, cb, (char *)password);
	if (pkey) {
		fclose(fp);
		return pkey;
	}
	fseek(fp, 0L, SEEK_SET);

	/* try to read DER format private key file */
	pkey = d2i_PrivateKey_fp(fp, NULL);

	fclose(fp);
	return pkey;
}

/**
 *	Load a X509_CRL object from CRL file @crlfile.
 *
 *	Return X509_CRL object if success, NULL on error.
 */
static X509_CRL * 
_su_load_x509_crl(const char *crlfile)
{
	X509_CRL *crl;
	FILE *fp = NULL;

	if (!crlfile)
		_SU_ERR_RET(NULL, "invalid argument @crlfile\n");

	fp = fopen(crlfile, "r");
	if (!fp)
		_SU_ERR_RET(NULL, "open crlfile %s failed: %s\n", 
			    crlfile, _SU_ERR_STR);

	crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL);
	if (crl) {
		fclose(fp);
		return crl;
	}
	fseek(fp, 0L, SEEK_SET);
	
	crl = d2i_X509_CRL_fp(fp, NULL);
	fclose(fp);
	
	return crl;
}

ssl_ctx_t * 
ssl_ctx_alloc(ssl_sd_e side, int method)
{
	ssl_ctx_t *sc;
	const SSL_METHOD *mt;
	long opt;

	if (_su_locks == NULL)
		_SU_ERR_RET(NULL, "init pthread safe lock failed\n");

	if (method == 0)
		method = SSL_V23;

	sc = calloc(1, sizeof(ssl_ctx_t));
	if (!sc)
		_SU_ERR_RET(NULL, "calloc failed: %s\n", _SU_ERR_STR);

	if (side == SSL_SD_CLIENT)
		mt = SSLv23_client_method();
	else if (side == SSL_SD_SERVER)
		mt = SSLv23_server_method();
	else {
		free(sc);
		_SU_ERR_RET(NULL, "invalid @side value %d\n", side);
	}
	
	sc->ctx = SSL_CTX_new(mt);
	if (!sc->ctx) {
		free(sc);
		_SU_ERR_RET(NULL, "SSL_CTX_new failed\n");
	}
	
	/* set info callback */
	SSL_CTX_set_info_callback(sc->ctx, _su_info_cb);

	/* set temp DH, from Openssl apps/s_server.c */
	SSL_CTX_set_tmp_dh(sc->ctx, _su_get_dh512());

	/* set ciphers to add SSLv2 method */
	if (SSL_CTX_set_cipher_list(sc->ctx, "ALL") != 1) {
		SSL_CTX_free(sc->ctx);
		free(sc);
		_SU_ERR_RET(NULL, "set cipher ALL failed\n");
	}

	/* set auto-reply for renegoatiate */
	SSL_CTX_set_mode(sc->ctx, SSL_MODE_AUTO_RETRY);

	/* remove unused method */
	opt = SSL_CTX_get_options(sc->ctx);

	if ( !(method & SSL_V2)) 
		opt |= SSL_OP_NO_SSLv2;

	if ( !(method & SSL_V3)) 
		opt |= SSL_OP_NO_SSLv3;

	if ( !(method & SSL_V10)) 
		opt |= SSL_OP_NO_TLSv1;

	if ( !(method & SSL_V11)) 
		opt |= SSL_OP_NO_TLSv1_1;

	if ( !(method & SSL_V12)) 
		opt |= SSL_OP_NO_TLSv1_2;

	SSL_CTX_set_options(sc->ctx, opt);

	/* set callback function for SNI */
	if (side == SSL_SD_SERVER) 
		SSL_CTX_set_tlsext_servername_callback(sc->ctx, _su_sni_cb);

	sc->side = side;
	sc->renegotiate = 0;		/* not enable renegotiate by default */

	return sc;
}

void 
ssl_ctx_free(ssl_ctx_t *sc)
{
	if (!sc)
		return;

	if (sc->ctx)
		SSL_CTX_free(sc->ctx);

	free(sc);
	return;
}

int 
ssl_ctx_load_cert(ssl_ctx_t *sc, const char *certfile, 
		  const char *keyfile, const char *password)
{
	X509 *cert;
	EVP_PKEY *pkey;

	if (!sc || !sc->ctx || !certfile || !keyfile)
		return -1;
	
	cert = _su_load_x509(certfile);
	if (!cert)
		return -1;

	if (SSL_CTX_use_certificate(sc->ctx, cert) != 1)
		return -1;

	pkey = _su_load_pkey(keyfile, password);
	if (!pkey)
		return -1;

	if (SSL_CTX_use_PrivateKey(sc->ctx, pkey) != 1)
		return -1;

	if (SSL_CTX_check_private_key(sc->ctx) != 1)
		return -1;		

	return 0;
}

int 
ssl_ctx_load_intmedcert(ssl_ctx_t *sc, const char *certfile)
{
	X509 *x509=NULL;

	if (!sc || !sc->ctx || !certfile)
		return -1;

	x509 = _su_load_x509(certfile);
	if (!x509) 
		return -1;

	if (SSL_CTX_add_extra_chain_cert(sc->ctx, x509) != 1) 
		_SU_ERR_RET(-1, "load intermedia certificate %s failed\n", 
			    certfile);

	return 0;
}

int 
ssl_ctx_load_cacert(ssl_ctx_t *sc, const char *cafile)
{
	int flags = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;

	if (!sc || !sc->ctx || !cafile)
		_SU_ERR_RET(-1, "invalid argument\n");
	
	if (SSL_CTX_load_verify_locations(sc->ctx, (char *)cafile, NULL) != 1)
		_SU_ERR_RET(-1, "load CA certificate %s failed\n", cafile);

	if (!(sc->flags & SSL_VERIFY_CA)) {
		SSL_CTX_set_verify_depth(sc->ctx, 100);
		sc->flags |= SSL_VERIFY_CA;
	}

	if (sc->flags & SSL_VERIFY_ALL) {
		SSL_CTX_set_verify(sc->ctx, flags, _su_verify_cb);
		SSL_CTX_set_verify_depth(sc->ctx, 100);
	}

	return 0;
}

int 
ssl_ctx_load_crl(ssl_ctx_t *sc, const char *crlfile)
{
	X509_CRL *crl;
	X509_STORE *st;
	int flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;

	if (!sc || !sc->ctx || !crlfile)
		_SU_ERR_RET(-1, "invalid argument\n");
	
	crl = _su_load_x509_crl(crlfile);
	if (!crl)
		return -1;
	
	st = SSL_CTX_get_cert_store(sc->ctx);
	if (!st) {
		X509_CRL_free(crl);
		return -1;
	}

	if (X509_STORE_add_crl(st, crl) != 1) {
		X509_CRL_free(crl);
		_SU_ERR_RET(-1, "load CRL certificate %s failed\n", 
			    crlfile);
	}
	
	if (!(sc->flags & SSL_VERIFY_CRL)) {
		sc->flags |= SSL_VERIFY_CRL;
	}

	if (!(sc->flags & SSL_VERIFY_ALL))
		SSL_CTX_set_verify(sc->ctx, flags, _su_verify_cb);

	X509_CRL_free(crl);
	return 0;
}

int 
ssl_ctx_set_ciphers(ssl_ctx_t *sc, const char *ciphers)
{
	return 0;
}

int 
ssl_ctx_set_options(ssl_ctx_t *sc, int options)
{
	return 0;
}

int 
ssl_ctx_set_domain(ssl_ctx_t *sc, const char *domain)
{
	if (!sc || !domain)
		_SU_ERR_RET(-1, "invalid argument\n");

	strncpy(sc->domain, domain, sizeof(sc->domain) - 1);

	return 0;
}

int 
ssl_ctx_set_renegotiate(ssl_ctx_t *sc, int renegotiate)
{
	if (!sc || !sc->ctx || renegotiate < 0) 
		_SU_ERR_RET(-1, "invalid argument\n");

	sc->renegotiate = renegotiate;

	return 0;
}

SSL *  
ssl_accept(ssl_ctx_t *sc, int fd, ssl_wt_e *wait)
{
	int ret;
	int err;
	SSL *ssl;
	ssl_info_t *si;

	if (!sc || !sc->ctx || fd < 0 || !wait)
		_SU_ERR_RET(NULL, "invalid argument\n");;

	/* create new SSL object */
	ssl = SSL_new(sc->ctx);
	if (!ssl) 
		_SU_ERR_RET(NULL, "SSL_new failed\n");
	
	if (SSL_set_fd(ssl, fd) != 1) {
		SSL_free(ssl);
		_SU_ERR_RET(NULL, "SSL_set_fd failed\n");
	}

	si = OPENSSL_malloc(sizeof(ssl_info_t));
	if (!si) {
		SSL_free(ssl);
		_SU_ERR_RET(NULL, "OPENSSL_malloc failed\n");		
	}

	si->ctx = sc;
	si->side = SSL_SD_SERVER;
	si->num_handshake = 0;
	si->in_handshake = 0;
	si->in_renegotiate = 0;

	if (SSL_set_app_data(ssl, si) != 1) {
		SSL_free(ssl);
		OPENSSL_free(si);
		_SU_ERR_RET(NULL, "SSL_set_app_data failed\n");
	}

	*wait = SSL_WT_NONE;

	ret = SSL_accept(ssl);
	
	/* disable renegotiate */
	if (sc->renegotiate == 0) {
		ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
	}

	if (ret == 1)
		return ssl;

	if (ret == 0) {
		SSL_free(ssl);
		_SU_ERR_RET(NULL, "SSL accept IO failed\n");
	}
	
	err = SSL_get_error(ssl, ret);

	if (err == SSL_ERROR_WANT_READ)
		*wait = SSL_WT_READ;
	else if (err == SSL_ERROR_WANT_WRITE)
		*wait = SSL_WT_WRITE;
	else {
		SSL_free(ssl);
		OPENSSL_free(si);
		_SU_ERR_RET(NULL, "SSL accept failed\n");
	}
	
	return ssl;
}

SSL * 
ssl_connect(ssl_ctx_t *sc, int fd, ssl_wt_e *wait)
{
	int ret;
	int err;
	SSL *ssl;
	ssl_info_t *si;

	if (!sc || !sc->ctx || fd < 0 || !wait)
		_SU_ERR_RET(NULL, "invalid argument\n");

	*wait = SSL_WT_NONE;

	/* alloc new SSL object */
	ssl = SSL_new(sc->ctx);
	if (!ssl)
		_SU_ERR_RET(NULL, "SSL_new failed\n");

	if (SSL_set_fd(ssl, fd) != 1) {
		SSL_free(ssl);
		_SU_ERR_RET(NULL, "SSL_set_fd failed\n");
	}

	si = OPENSSL_malloc(sizeof(ssl_info_t));
	if (!si) {
		SSL_free(ssl);
		_SU_ERR_RET(NULL, "OPENSSL_malloc failed\n");
	}

	/* set domain name */
	if (sc->domain[0]) {
		if (SSL_set_tlsext_host_name(ssl, sc->domain) != 1) {
			SSL_free(ssl);
			OPENSSL_free(si);
			_SU_ERR_RET(NULL, "SSL_set_tlsext_host_name failed\n");
		}
	}

	si->ctx = sc;
	si->side = SSL_SD_CLIENT;
	si->num_handshake = 0;
	si->in_handshake = 0;

	if (SSL_set_app_data(ssl, si) != 1) {
		SSL_free(ssl);
		OPENSSL_free(si);
		_SU_ERR_RET(NULL, "SSL_set_app_data failed\n");
	}

	ret = SSL_connect(ssl);

	/* disable renegotiate */
	if (sc->renegotiate < 1) {
		ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
	}

	/* connect success */
	if (ret == 1)
		return ssl;

	/* low-layer I/O closed or SSL shutdowned */
	if (ret == 0) {
		SSL_free(ssl);
		OPENSSL_free(si);
		_SU_ERR_RET(NULL, "SSL connect IO failed\n");
	}

	err = SSL_get_error(ssl, ret);

	if (err == SSL_ERROR_WANT_READ)
		*wait = SSL_WT_READ;
	else if (err == SSL_ERROR_WANT_WRITE)
		*wait = SSL_WT_WRITE;
	else {
		SSL_free(ssl);
		OPENSSL_free(si);
		_SU_ERR_RET(NULL, "SSL connect failed\n");
	}

	return ssl;
}

int 
ssl_handshake(SSL *ssl, ssl_wt_e *wait)
{
	int ret;
	int err;
	ssl_ctx_t *sc;
	ssl_info_t *si;

	if (!ssl || !wait) 
		_SU_ERR_RET(-1, "invalid argument\n");

	si = SSL_get_app_data(ssl);
	if (!si)
		_SU_ERR_RET(-1, "get app data failed\n");

	sc = si->ctx;
	if (!sc)
		_SU_ERR_RET(-1, "get ssl_ctx failed\n");

	*wait = SSL_WT_NONE;

	ret = SSL_do_handshake(ssl);
	
	if (ret == 1) {
		/* server side in renegotiate */
		if (si->in_renegotiate && si->side == SSL_SD_SERVER) {
			if (!(ssl->state & SSL_ST_ACCEPT)) {
				ssl->state = SSL_ST_ACCEPT;
				si->in_renegotiate = 0;
				return ssl_handshake(ssl, wait);
			}
		}
		si->in_renegotiate = 0;
		return 0;
	}

	if (ret == 0)
		_SU_ERR_RET(-1, "SSL handshake IO failed\n");

	err = SSL_get_error(ssl, ret);
	if (err == SSL_ERROR_WANT_READ)
		*wait = SSL_WT_READ;
	else if (err == SSL_ERROR_WANT_WRITE)
		*wait = SSL_WT_WRITE;
	else 
		_SU_ERR_RET(-1, "SSL handshake failed %d\n", err);

	return 0;
}

int 
ssl_renegotiate(SSL *ssl, ssl_wt_e *wait)
{
	int ret;
	ssl_ctx_t *sc;
	ssl_info_t *si;


	if (!ssl || !wait) 
		_SU_ERR_RET(-1, "invalid argument\n");

	si = SSL_get_app_data(ssl);
	if (!si)
		_SU_ERR_RET(-1, "get app data failed\n");

	sc = si->ctx;
	if (!sc)
		_SU_ERR_RET(-1, "get ssl_ctx failed\n");

	*wait = SSL_WT_NONE;
	si->in_renegotiate = 1;

	/* do SSL renegotiate */
	ret = SSL_renegotiate(ssl);
	if (ret < 1) 
		_SU_ERR_RET(-1, "SSL renegotiate failed\n");

	/* call SSL handshake finished work */
	return ssl_handshake(ssl, wait);
}

int 
ssl_recv(SSL *ssl, void *buf, int len, int *closed, int *handshake)
{
	int n;
	int err;
	ssl_ctx_t *sc;
	ssl_info_t *si;

	if (!ssl || !buf || len < 1 || !closed || !handshake)
		_SU_ERR_RET(-1, "invalid argument");

	*closed = 0;
	*handshake = 0;

	/* check renegotiate */
	si = SSL_get_app_data(ssl);
	if (!si) {
		_SU_ERR_RET(-1, "get app data failed\n");
	}
	sc = si->ctx;
	if (!sc) {
		_SU_ERR_RET(-1, "get ssl_ctx failed\n");
	}

	n = SSL_read(ssl, buf, len);

	if (si->in_handshake) {
		if (sc->renegotiate == 0) 
			_SU_ERR_RET(-1, "not support renegotiate\n");
		*handshake = 1;
		return 0;
	}
	
	/* recv success */
	if (n > 0)
		return n;

	if (n == 0) {
		/* low-layer I/O failed, or something I don't know */
		*closed = 1;
		return 0;
	}

	/* check blocked by read */
	err = SSL_get_error(ssl, n);
	if (err != SSL_ERROR_WANT_READ) {
		return -1;
	}

	return n;
}

int 
ssl_send(SSL *ssl, const void *buf, int len)
{
	int n;
	int err;
	int ret;
	ssl_ctx_t *sc;
	ssl_info_t *si;

	if (!ssl || !buf || len < 0)
		_SU_ERR_RET(-1, "invalid argument\n");

	/* check renegotiate */
	si = SSL_get_app_data(ssl);
	if (!si) {
		_SU_ERR_RET(-1, "get app data failed\n");
	}
	sc = si->ctx;
	if (!sc) {
		_SU_ERR_RET(-1, "get ssl_ctx failed\n");
	}

	/* not enable renegotiate but renegotiate occured */
	if (sc->renegotiate == 0 && si->num_handshake > 1) {
		return -1;
	}

	n = SSL_write(ssl, buf, len);
	if (n == 0) {
		/* the SSL connection is closed */
		ret = SSL_get_shutdown(ssl);
		if (ret & (SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN)) {
			return -1;
		}

		/* underlying transport is shutdown by peer, we think 
		 * it's error and never call ssl_shutdown 
		 */
		err = SSL_get_error(ssl, n);
		if (err == SSL_ERROR_ZERO_RETURN)
			return -1;

		return -1;
	}
	else if (n < 0) {
		err = SSL_get_error(ssl, n);
		/* the SSL record or finished */
		if (err == SSL_ERROR_WANT_READ) {
			return 0;
		}
		/* the SSL renegotiate not finished */
		else if (err == SSL_ERROR_WANT_WRITE) {
			return 0;
		}
		
		return -1;
	}

	return n;
}

int 
ssl_shutdown(SSL *ssl, ssl_wt_e *wait)
{
	int ret;
	int err;

	if (!ssl || !wait)
		_SU_ERR_RET(-1, "invalid argument\n");

	*wait = SSL_WT_NONE;

	ret = SSL_shutdown(ssl);

	if (ret == 1)
		return 0;

	if (ret == 0) {
		*wait = SSL_WT_WRITE;
		return 0;
	}

	err = SSL_get_error(ssl, ret);
	if (err == SSL_ERROR_WANT_READ)
		*wait = SSL_WT_READ;
	else if (err == SSL_ERROR_WANT_WRITE)
		*wait = SSL_WT_WRITE;
	else 
		_SU_ERR_RET(-1, "SSL shutdown failed\n");

	return 0;
}

int 
ssl_free(SSL *ssl)
{
	ssl_ctx_t *sc;
	ssl_info_t *si;

	if (!ssl)
		_SU_ERR_RET(-1, "invalid argument\n");

	/* check renegotiate */
	si = SSL_get_app_data(ssl);
	if (!si) {
		_SU_ERR_RET(-1, "get app data failed\n");
	}
	sc = si->ctx;
	if (!sc)
		_SU_ERR_RET(-1, "get ssl_ctx failed\n");

	OPENSSL_free(si);
	SSL_free(ssl);

	return 0;
}

static void __attribute__((constructor)) 
_ssl_global_init(void)
{
	int i;
	int nlock;

	CRYPTO_malloc_init();
	SSL_library_init();
	ERR_load_BIO_strings();
	SSL_load_error_strings();
	OpenSSL_add_all_algorithms();

	/* alloc locks for thread safe */
	nlock = CRYPTO_num_locks();
	_su_locks = OPENSSL_malloc(nlock * sizeof(pthread_mutex_t));
	if (_su_locks) {
		for (i = 0; i < nlock; i++) 
			pthread_mutex_init(&(_su_locks[i]), NULL);
	}
}


